iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 21
1
自我挑戰組

Ruby on Rails 與它們相關的東西系列 第 21

Day21 - Ruby on Rails 中常見的 N+1 與解法

  • 分享至 

  • xImage
  •  

前言

在 Ruby on Rails 中,透過 ORM (Object Relational Mapping) 使我們可以輕易地對不同表進行操作,方便之餘,一不小心就可能會寫出 N+1

  1. 到底什麼是 N+1 ?
  2. 該如何解決 ?

後續的文章會以此 repo 作為範例


介紹

N+1 指的是 SQL 撈資料時,明明可以一次撈完 (ex: 3 筆資料),卻使用逐筆撈資料的方式處理 (每次只撈 1 筆資料)

白話文: 使用者要在同間商店買 3 件商品,可一次採買完成,卻分成 3 次購買 (除非是想收集發票

分 3 次購買便是 N ,而 +1 則是使用者決定要去買的那次本身

導致總共撈了 3+1 次,嚴重影響效能

直接看例子,會比較好懂,想看 N+1 本人的話,可參考此 commit

# app/models/user.rb
class User < ApplicationRecord
  has_many :orders
end

# app/models/order.rb
class Order < ApplicationRecord
  belongs_to :user
end

# app/controllers/users_controller.rb
def index
  @users = User.all
end

# app/views/users/index.html.erb:24
<td><%= user.orders.sum(&:total_price) %></td>o

上述可以正常運作,但也造成了 N+1 的問題發生

# Order 被撈了 3 次

User Load (0.3ms)  SELECT "users".* FROM "users"
Order Load (0.2ms)  SELECT "orders".* FROM "orders" WHERE "orders"."user_id" = $1  [["user_id", 1]]
Order Load (0.2ms)  SELECT "orders".* FROM "orders" WHERE "orders"."user_id" = $1  [["user_id", 2]]
Order Load (0.3ms)  SELECT "orders".* FROM "orders" WHERE "orders"."user_id" = $1  [["user_id", 3]]

N+1 本人

如何解決 N+1

最簡單方法,使用 includes,可參考此 commit

雖然每次會撈回整筆 orders 資訊 (假如我只需要 order.total_price 的資訊,其餘欄位皆不用的話),會撈比較多東西回來,資料量如果不多的話,includes 是一種解法,資料量多時,會有不同解法 (未來有機會再另寫文章探討

# app/controllers/users_controller.rb
def index
  @users = User.includes(:orders)
end
# Order 變成一次查詢完畢

User Load (0.2ms)  SELECT "users".* FROM "users"
Order Load (0.4ms)  SELECT "orders".* FROM "orders" WHERE "orders"."user_id" IN ($1, $2, $3)  [["user_id", 1], ["user_id", 2], ["user_id", 3]]

includes 解 N+1

如何知道哪些地方有 N+1

可使用 Bullet 這個 Gem,並設定提醒視窗,方便知道是否有 N+1 的問題發生

安裝方式

在 Gemfile 加入以下,可參考此 commit

# Gemfile

gem 'bullet', group: 'development'

# 記得要 bundle

接著在 development.rb 檔案增加參數設定,可參考此 commit

備註: 更多設定可至官方網站查詢

# config/environments/development.rb

Bullet.enable = true
Bullet.bullet_logger = true
Bullet.console = true
Bullet.add_footer = true

# 接著重啟 rails server

若有 N+1 會有提醒視窗

參考資料

Ruby on Rails - 用 Include 和 Join 避免 N+1 Query


小結

造成網站效能拖慢有許多可能,N+1 只是其中一種

本篇是淺談其中的 1 種解法,寫這篇時,發現類似文章不下 5 篇以上,若想深入了解,歡迎善用 Google


鐵人賽文章連結:https://ithelp.ithome.com.tw/articles/10244142
medium 文章連結:https://link.medium.com/w4c3ApZw49
本文同步發布於 小菜的 Blog https://riverye.com/

備註:之後文章修改更新,以個人部落格為主


上一篇
Day20 - Ruby on Rails 測試篇 - Cucumber (內文有範例教如何寫中文測試)
下一篇
Day22 - Ruby on Rails 中的 Race Condition
系列文
Ruby on Rails 與它們相關的東西31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言